package server

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"ngrok/log"
	"os"
	"os/user"
	"path"
	"reflect"
	"regexp"
)

var jsonData map[string]interface{}

type DBConfig struct {
	Dialect         string
	Database        string
	User            string
	Password        string
	Host            string
	Port            int
	Charset         string
	URL             string
	MaxIdleConns    int
	MaxOpenConns    int
	ConnMaxLifetime int64
	Sslmode         string
}

func setField(obj interface{}, name string, value interface{}) error {
	structData := reflect.ValueOf(obj).Elem()
	fieldValue := structData.FieldByName(name)

	if !fieldValue.IsValid() {
		return fmt.Errorf("utils.setField() No such field: %s in obj ", name)
	}

	if !fieldValue.CanSet() {
		return fmt.Errorf("Cannot set %s field value ", name)
	}

	fieldType := fieldValue.Type()
	val := reflect.ValueOf(value)

	valTypeStr := val.Type().String()
	fieldTypeStr := fieldType.String()
	if valTypeStr == "float64" && fieldTypeStr == "int" {
		val = val.Convert(fieldType)
	} else if fieldType != val.Type() {
		return fmt.Errorf("Provided value type " + valTypeStr + " didn't match obj field type " + fieldTypeStr)
	}
	fieldValue.Set(val)
	return nil
}

func SetStructByJSON(obj interface{}, mapData map[string]interface{}) error {
	for key, value := range mapData {
		if err := setField(obj, key, value); err != nil {
			fmt.Println(err.Error())
			return err
		}
	}
	return nil
}

func defaultPath() string {
	user, err := user.Current()

	// user.Current() does not work on linux when cross compiling because
	// it requires CGO; use os.Getenv("HOME") hack until we compile natively
	homeDir := os.Getenv("HOME")
	if err != nil {
		log.Warn("Failed to get user's home directory: %s. Using $HOME: %s", err.Error(), homeDir)
	} else {
		homeDir = user.HomeDir
	}

	return path.Join(homeDir, ".ngrok")
}

func LoadDbConfig(opts *Options) (config *DBConfig, err error) {
	configPath := opts.dbConfig
	if configPath == "" {
		configPath = defaultPath()
	}

	log.Info("Reading configuration file %s", configPath)
	bytes, err := ioutil.ReadFile(configPath)
	if err != nil {
		// failure to read a configuration file is only a fatal error if
		// the user specified one explicitly
		if opts.dbConfig != "" {
			err = fmt.Errorf("Failed to read configuration file %s: %v", configPath, err)
			return &dBConfig, err
		}
	}

	// deserialize/parse the config
	configStr := string(bytes[:])
	reg := regexp.MustCompile(`/\*.*\*/`)

	configStr = reg.ReplaceAllString(configStr, "")
	bytes = []byte(configStr)
	if err := json.Unmarshal(bytes, &jsonData); err != nil {
		fmt.Println("invalid config: ", err.Error())
		dBConfig.URL = ""
		return &dBConfig, err
	}
	SetStructByJSON(&dBConfig, jsonData["database"].(map[string]interface{}))
	url := ""
	if dBConfig.Dialect == "mysql" {
		url = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local", dBConfig.User, dBConfig.Password, dBConfig.Host, dBConfig.Port, dBConfig.Database, dBConfig.Charset)
	}
	if dBConfig.Dialect == "postgresql" {
		url = fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=%s", dBConfig.Host, dBConfig.Port, dBConfig.User, dBConfig.Database, dBConfig.Password, dBConfig.Sslmode)
	}
	if dBConfig.Dialect == "sqlite3" || dBConfig.Dialect == "sqlite" {
		url = dBConfig.Database
	}
	if dBConfig.Dialect == "mssql" {
		url = fmt.Sprintf("sqlserver://%s:%s@%s:%s?database=%s", dBConfig.User, dBConfig.Password, dBConfig.Host, dBConfig.Port, dBConfig.Database)

	}
	dBConfig.URL = url
	log.Info("Read configuration file %s", dBConfig.URL)
	return &dBConfig, err
}

// DBConfig 数据库相关配置
var dBConfig DBConfig
